home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Libris Britannia 4
/
science library(b).zip
/
science library(b)
/
PROGRAMM
/
PASCAL
/
0826.ZIP
/
EXEUTL.ARC
/
PACK.PAS
< prev
Wrap
Pascal/Delphi Source File
|
1987-11-17
|
12KB
|
344 lines
{
PACK reduces the size of EXE files by packing the EXE header table
into a smaller structure. It does so by using its own fixup relocator,
and building a table of fixups without redundant segment
information as occurs in the DOS standard format.
PACK will also report how much space it could save by run-length
encoding repeated byte sequences. To see this effect, set the
constant ShowRLEeffect to True. PACK does not actually implement
this kind of packing at this time.
PACK works in a manner similar to EXEPACK (from Microsoft) and
SPMAKER (from Realia).
After compiling, just enter PACK to get directions for usage.
Version 1.0.
Written 11/87, Kim Kokkonen, TurboPower Software.
Compuserve 72457,2131.
Released to the public domain.
}
{$S-,I-,R-}
program Pack;
{-Packs EXE file header structure}
function StUpcase(S : string) : string;
{-Return uppercase of string}
var
I : integer;
begin
for I := 1 to length(S) do
S[I] := upcase(S[I]);
StUpcase := S;
end;
function HasExtension(Name : string; var DotPos : Word) : Boolean;
{-Return whether and position of extension separator dot in a pathname}
var
I : Word;
begin
DotPos := 0;
for I := Length(Name) downto 1 do
if (Name[I] = '.') and (DotPos = 0) then
DotPos := I;
HasExtension := (DotPos > 0) and (Pos('\', Copy(Name, Succ(DotPos), 64)) = 0);
end;
function ForceExtension(Name, Ext : string) : string;
{-Return a pathname with the specified extension attached}
var
DotPos : Word;
begin
if HasExtension(Name, DotPos) then
ForceExtension := Copy(Name, 1, DotPos)+Ext
else
ForceExtension := Name+'.'+Ext;
end;
procedure Error(Msg : string);
{-Write error message and halt}
begin
if Msg <> '' then
WriteLn(^M^J, Msg);
Halt(1);
end;
function BlkRead(var F : file; var Buffer; Size : Word) : Boolean;
{-Convenient shell around BlockRead}
var
BytesRead : Word;
begin
BlockRead(F, Buffer, Size, BytesRead);
BlkRead := (IoResult = 0) and (BytesRead = Size);
end;
function BlkWrite(var F : file; var Buffer; Size : Word) : Boolean;
{-Convenient shell around BlockWrite}
var
BytesWritten : Word;
begin
BlockWrite(F, Buffer, Size, BytesWritten);
BlkWrite := (IoResult = 0) and (BytesWritten = Size);
end;
procedure PackExe(ExeName, OutName : string);
{-Squeeze an EXE file by packing fixups into segment groups}
const
MaxRWbufSize = $8000; {Max size of read/write buffer for EXE copying}
FlagWord = $FFFF; {Flag segment changes in packed relocation table}
OrigIPofs = 3; {Position of first patch word in NewLoader}
ShowRLEeffect = False; {True to show value of run length encoding}
Threshold = 4; {Bytes of overhead per RLE block}
MaxReloc = $3FFC; {Maximum allowable relocation items}
NewLoaderSize = 82;
NewLoader : array[1..NewLoaderSize] of Byte =
{This is a dump of the COM file generated by assembling NEWLOAD.ASM}
(
$EB, $08, $00, $00, $00, $00, $00, $00, $00, $00, $2E, $8C, $1E, $06, $00, $2E,
$8C, $06, $08, $00, $8C, $C3, $83, $C3, $10, $8C, $C8, $8E, $D8, $BE, $52, $00,
$FC, $AD, $3D, $FF, $FF, $75, $0B, $AD, $3D, $FF, $FF, $74, $0C, $03, $C3, $8E,
$C0, $AD, $8B, $F8, $26, $01, $1D, $EB, $E8, $2E, $8E, $06, $08, $00, $2E, $8E,
$1E, $06, $00, $8B, $C3, $2E, $03, $06, $04, $00, $50, $2E, $A1, $02, $00, $50,
$CB, $90
);
type
ExeHeaderRec = {Information describing EXE file}
record
Signature : Word; {EXE file signature}
LengthRem : Word; {Number of bytes in last page of EXE image}
LengthPages : Word; {Number of 512 byte pages in EXE image}
NumReloc : Word; {Number of relocation items}
HeaderSize : Word; {Number of paragraphs in EXE header}
MinHeap, MaxHeap : Word; {Paragraphs to keep beyond end of image}
StackSeg, StackPtr : Word; {Initial SS:SP, StackSeg relative to image base}
CheckSum : Word; {EXE file check sum, not used}
IpInit, CodeSeg : Word; {Initial CS:IP, CodeSeg relative to image base}
RelocOfs : Word; {Bytes into EXE for first relocation item}
OverlayNum : Word; {Overlay number, not used here}
end;
RelocRec =
record
Offset : Word;
Segment : Word;
end;
RelocArray = array[1..MaxReloc] of RelocRec;
PackedTable = array[1..$7FF0] of Word;
ReadWriteBuffer = array[1..MaxRWbufSize] of Byte;
var
ExeF, OutF : file;
BytesRead, BytesWritten, RWbufSize,
I, TableSize, TablePos, LastSeg,
BlockSize, OldNumReloc, OldHeaderSize : Word;
OldExeSize, ExeSize, RLEbytes : LongInt;
LastByte : Byte;
ExeHeader : ExeHeaderRec;
RA : ^RelocArray; {Old relocation table from input file}
PT : ^PackedTable; {New relocation table after packing}
RWbuf : ^ReadWriteBuffer; {Read/write buffer for file copy}
procedure SetTable(var TA : PackedTable; var TablePos : Word; Value : Word);
{-Put a value into packed table and increment the index}
begin
TA[TablePos] := Value;
Inc(TablePos);
end;
begin
{Make sure we don't overwrite the input}
if StUpcase(ExeName) = StUpcase(OutName) then
Error('Input and output files must differ');
{Open the existing EXE file}
Assign(ExeF, ExeName);
Reset(ExeF, 1);
if IoResult <> 0 then
Error(ExeName+' not found');
{Read the existing EXE header}
if not BlkRead(ExeF, ExeHeader, SizeOf(ExeHeaderRec)) then
Error('Error reading EXE file');
with ExeHeader do begin
{Assure it's a real EXE file}
if Signature <> $5A4D then
Error('File is not in EXE format');
{Check the number of relocation items}
if NumReloc = 0 then
Error('No packing can be done. No output written');
if NumReloc > MaxReloc then
Error('Number of relocation items exceeds capacity of PACK');
if NumReloc shl 2 > MaxAvail then
Error('Insufficient memory');
{Read the relocation items into memory}
GetMem(RA, NumReloc shl 2);
Seek(ExeF, RelocOfs);
if not BlkRead(ExeF, RA^, NumReloc shl 2) then
Error('Error reading EXE file');
{Determine size of packed relocation table in bytes}
LastSeg := $FFFF;
TableSize := 0;
for I := 1 to NumReloc do
with RA^[I] do begin
if Segment <> LastSeg then begin
LastSeg := Segment;
{Table will hold FFFF as a flag, followed by new segment}
Inc(TableSize, 4);
end;
{Space for the offset in this record}
Inc(TableSize, 2);
end;
{Termination record}
Inc(TableSize, 4);
{Build the packed relocation table in memory}
if TableSize > MaxAvail then
Error('Insufficient memory');
GetMem(PT, TableSize);
LastSeg := $FFFF;
TablePos := 1;
for I := 1 to NumReloc do
with RA^[I] do begin
if Segment <> LastSeg then begin
LastSeg := Segment;
{Flag that the segment is changing}
SetTable(PT^, TablePos, FlagWord);
{Write the new segment}
SetTable(PT^, TablePos, Segment);
end;
{Write the offset in the segment}
SetTable(PT^, TablePos, Offset);
end;
{Write a termination record}
for I := 1 to 2 do
SetTable(PT^, TablePos, FlagWord);
{Deallocate space for the old relocation array}
FreeMem(RA, NumReloc shl 2);
{Allocate space for the read/write buffer}
if MaxAvail > MaxRWbufSize then
RWbufSize := MaxRWbufSize
else
RWbufSize := MaxAvail;
GetMem(RWbuf, RWbufSize);
{Save some items we'll need later}
OldNumReloc := NumReloc; {items}
OldHeaderSize := HeaderSize; {paragraphs}
if LengthRem = 0 then
OldExeSize := LongInt(LengthPages) shl 9
else
OldExeSize := (LongInt(Pred(LengthPages)) shl 9)+LongInt(LengthRem);
{Change the header to accomodate the packing}
{No fixups remain after packing}
NumReloc := 0;
{Headersize shrinks to size of header record, rounded to para boundary}
HeaderSize := (SizeOf(ExeHeaderRec)+15) shr 4; {paragraphs}
{Patch initial CS:IP into the new loader}
Move(IpInit, NewLoader[OrigIPofs], 4);
{Set up so our loader executes first}
IpInit := 0;
CodeSeg := Succ(OldExeSize shr 4)-OldHeaderSize; {paragraphs}
{Compute new exesize}
ExeSize := (LongInt(HeaderSize)+LongInt(CodeSeg)) shl 4
+LongInt(NewLoaderSize)+LongInt(TableSize); {bytes}
if ExeSize >= OldExeSize then
Error('Packed size exceeds original. No output written');
if (ExeSize and 511) = 0 then begin
{An exact number of pages}
LengthPages := ExeSize shr 9;
LengthRem := 0;
end else begin
LengthPages := Succ(ExeSize shr 9);
LengthRem := ExeSize-LongInt(Pred(LongInt(LengthPages)) shl 9);
end;
{Create the new EXE file}
Assign(OutF, OutName);
Rewrite(OutF, 1);
if IoResult <> 0 then
Error('Could not create '+OutName);
{Write the new header}
if not BlkWrite(OutF, ExeHeader, (HeaderSize shl 4)) then
Error('Error writing EXE file');
{Transfer the code from old to new program}
Seek(ExeF, OldHeaderSize shl 4);
{Initialize parameters for run length encoding}
LastByte := 0;
BlockSize := 0;
RLEbytes := 00;
repeat
BlockRead(ExeF, RWbuf^, RWbufSize, BytesRead);
if IoResult <> 0 then
Error('Error reading EXE file');
if BytesRead <> 0 then begin
if not BlkWrite(OutF, RWbuf^, BytesRead) then
Error('Error writing EXE file');
if ShowRLEeffect then
{Check to see how much run length packing would save}
for I := 1 to BytesRead do
if RWbuf^[I] = LastByte then
Inc(BlockSize)
else begin
LastByte := RWbuf^[I];
if BlockSize > Threshold then
Inc(RLEbytes, BlockSize-Threshold);
BlockSize := 0;
end;
end;
until BytesRead = 0;
if ShowRLEeffect then
if BlockSize > Threshold then
Inc(RLEbytes, BlockSize-Threshold);
{Write the loader to the new program}
Seek(OutF, (LongInt(HeaderSize)+LongInt(CodeSeg)) shl 4);
if not BlkWrite(OutF, NewLoader, NewLoaderSize) then
Error('Error writing EXE file');
{Write the packed loader table to the program}
if not BlkWrite(OutF, PT^, TableSize) then
Error('Error writing EXE file');
if ShowRLEeffect then
WriteLn('Run length packing would save ', RLEbytes, ' bytes');
end;
{Release heap space we allocated}
FreeMem(PT, TableSize);
FreeMem(RWbuf, RWbufSize);
{Close up the files}
Close(ExeF);
Close(OutF);
end;
begin
Writeln('PACK 1.0, by TurboPower Software');
if ParamCount < 2 then
Error('Usage: PACK OldExeName NewExeName');
{Modify the EXE file}
PackExe(ForceExtension(ParamStr(1), 'EXE'), ForceExtension(ParamStr(2), 'EXE'));
end.